#include "defaults.h"
#include "trianglemeshrenderer.h"
#include "utils.h"

using namespace std;

TriangleMeshRenderer::TriangleMeshRenderer(STriangleMesh* primitive, 
													 const Vec3n& color) 
	: PrimitiveRenderer(primitive),
	  color(color),
	  normalsign(1.0)
{
	list = -1;
	listWireframe = -1;

	for(size_t i = 0; i < primitive->SizeElements(); i++)
		element.push_back(static_cast<STriangle*>(primitive->GetElement(i)));

	positionAttribute = "position";	

	Init();
}

TriangleMeshRenderer::~TriangleMeshRenderer() {
	glDeleteLists(list, 1);
	glDeleteLists(listWireframe, 1);
}
	
void TriangleMeshRenderer::UpdateFilledMesh() {
	if(list != -1)
		glDeleteLists(list, 1);
	list = glGenLists(1);
	glNewList(list, GL_COMPILE);
	
	glMatrixMode(GL_TEXTURE);
	glPushMatrix();
	glLoadIdentity();
	glTranslatef(0.0, 0.0, 0.0);
	glScalef(1.0, 1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
	glPolygonOffset(1.0, 1.0);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPushAttrib(GL_POLYGON_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	GLfloat mat_diffuse[4] = {color[0], color[1], color[2], 1.0};
	GLfloat mat_white[4] = {1.0, 1.0, 1.0, 1.0};
	GLfloat mat_cyan[4] = {0.0, 1.0, 1.0, 1.0};
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse); 
	size_t iu = 0;
	size_t iv = 1;
	std::string paramU = "u";
	std::string paramV = "v";

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glBegin(GL_TRIANGLES);
	for(size_t i = 0; i < element.size(); i++) {
		STriangle* t = element[i];
		Vec3n color = math::V3n(0.75, 0.75, 0.75);
		if(t->Exist("mark"))
			color = PrimitiveRenderer::Color(t->Get<size_t>("mark"));
		else
			color = math::V3n(0.75, 0.75, 0.75);

		mat_diffuse[0] = color[0];
		mat_diffuse[1] = color[1];
		mat_diffuse[2] = color[2];
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse); 
		glColor3f(color[0], color[1], color[2]);

		const Vec3n& a = t->vertex[0]->Get<Vec3n>(positionAttribute);
		const Vec3n& b = t->vertex[1]->Get<Vec3n>(positionAttribute);
		const Vec3n& c = t->vertex[2]->Get<Vec3n>(positionAttribute);

		double taU, taV, tbU, tbV, tcU, tcV;
		taU = taV = tbU = tbV = tcU = tcV = 0.0;

		if(paramU != "") {
			taU = t->vertex[0]->Get<nfloat>(paramU);
			tbU = t->vertex[1]->Get<nfloat>(paramU);
			tcU = t->vertex[2]->Get<nfloat>(paramU);		
		}

		if(paramV != "") {
			taV = t->vertex[0]->Get<nfloat>(paramV);
			tbV = t->vertex[1]->Get<nfloat>(paramV);
			tcV = t->vertex[2]->Get<nfloat>(paramV);
		}

		Vec3n n = normalsign * cross(b - a, c - a);
		normalize(n);
		glNormal3f(n[0], n[2], n[1]);

		glTexCoord2f(taU, taV);
		//glNormal3f(na[0], na[2], na[1]);
		glVertex3f(a[0], a[2], a[1]);

		glTexCoord2f(tcU, tcV);
		//glNormal3f(nc[0], nc[2], nc[1]);
		glVertex3f(c[0], c[2], c[1]);
			
		glTexCoord2f(tbU, tbV);
		//glNormal3f(nb[0], nb[2], nb[1]);
		glVertex3f(b[0], b[2], b[1]);
	}
	glEnd();
	glDisable(GL_POLYGON_OFFSET_FILL);
	glDisable(GL_TEXTURE_2D);

	glMatrixMode(GL_TEXTURE);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopAttrib();
	
	glEndList();
}

void TriangleMeshRenderer::UpdateWireFrameMesh() {
	STriangleMesh* tm = static_cast< STriangleMesh* >(primitive);

	if(listWireframe != -1)
		glDeleteLists(listWireframe, 1);

	listWireframe = glGenLists(1);
	glNewList(listWireframe, GL_COMPILE);
	glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LINE_BIT);
	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glBegin(GL_LINES);
	for(size_t i = 0; i < tm->GetNumEdges(); i++) {
		const SEdge* edge = tm->Edge(i);
		const Vec3n& color = PrimitiveRenderer::Color(edge->Mark());

		const Vec3n& a = edge->a->Get<Vec3n>(positionAttribute);
		const Vec3n& b = edge->b->Get<Vec3n>(positionAttribute);

		glColor3f(color[0], color[1], color[2]);
		glVertex3f(a[0], a[2], a[1]);
		glVertex3f(b[0], b[2], b[1]);
	}
	glEnd();

	glPopAttrib();
	glEndList();
}

void TriangleMeshRenderer::Init() {
	cout << "update filled mesh." << endl;
	UpdateFilledMesh();
	cout << "update wireframe mesh." << endl;
	UpdateWireFrameMesh();
}
	
void TriangleMeshRenderer::Update() {
 	UpdateFilledMesh();
	UpdateWireFrameMesh();
}

void TriangleMeshRenderer::Render() const {
	glPushAttrib(GL_ENABLE_BIT);
	glCallList(list);
	glCallList(listWireframe);
	glPopAttrib();
}
